/*
 * $Id: WeakEventListenerList.java 3190 2009-01-20 17:47:52Z kschaefe $
 *
 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx.event;

import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;

/**
 * A class that holds a list of EventListeners. A single instance can be used to
 * hold all listeners (of all types) for the instance using the list. It is the
 * responsibility of the class using the EventListenerList to provide type-safe
 * API (preferably conforming to the JavaBeans spec) and methods which dispatch
 * event notification methods to appropriate Event Listeners on the list.
 * 
 * The main benefit that this class provides is that it releases garbage
 * collected listeners (internally uses weak references).
 * <p>
 * 
 * PENDING: serialization support
 * 
 *
 * Usage example: Say one is defining a class that sends out FooEvents, and one
 * wants to allow users of the class to register FooListeners and receive
 * notification when FooEvents occur. The following should be added to the class
 * definition:
 * 
 * <pre>
 * EventListenerList listenerList = new EventListenerList();
 * FooEvent fooEvent = null;
 * 
 * public void addFooListener(FooListener l) {
 *     listenerList.add(FooListener.class, l);
 * }
 * 
 * public void removeFooListener(FooListener l) {
 *     listenerList.remove(FooListener.class, l);
 * }
 * 
 * 
 * // Notify all listeners that have registered interest for
 * // notification on this event type.  The event instance 
 * // is lazily created using the parameters passed into 
 * // the fire method.
 * 
 * 
 * protected void fireFooXXX() {
 *     // Guaranteed to return a non-null array
 *     FooListener[] listeners = listenerList.getListeners(FooListener.class);
 *     // Process the listeners last to first, notifying
 *     // those that are interested in this event
 *     for (FooListener listener: listeners) {
 *             // Lazily create the event:
 *             if (fooEvent == null)
 *                 fooEvent = new FooEvent(this);
 *             listener.fooXXX(fooEvent);
 *         }
 *     }
 * }
 * </pre>
 * 
 * foo should be changed to the appropriate name, and fireFooXxx to the
 * appropriate method name. One fire method should exist for each notification
 * method in the FooListener interface.
 * <p>
 * <strong>Warning:</strong> Serialized objects of this class will not be
 * compatible with future Swing releases. The current serialization support is
 * appropriate for short term storage or RMI between applications running the
 * same version of Swing. As of 1.4, support for long term storage of all
 * JavaBeans<sup><font size="-2">TM</font></sup> has been added to the
 * <code>java.beans</code> package. Please see {@link java.beans.XMLEncoder}.
 *
 * @version 1.37 11/17/05
 * @author Georges Saab
 * @author Hans Muller
 * @author James Gosling
 */
public class WeakEventListenerList implements Serializable {

	protected transient List<WeakReference<? extends EventListener>> weakReferences;
	protected transient List<Class<? extends EventListener>> classes;

	/**
	 * Passes back the event listener list as an array of ListenerType-listener
	 * pairs. As a side-effect, cleans out any garbage collected listeners
	 * before building the array.
	 * 
	 * @return a array of listenerType-listener pairs.
	 */
	public Object[] getListenerList() {
		List<? extends EventListener> listeners = cleanReferences();
		Object[] result = new Object[listeners.size() * 2];
		for (int i = 0; i < listeners.size(); i++) {
			result[2 * i + 1] = listeners.get(i);
			result[2 * i] = getClasses().get(i);
		}
		return result;
	}

	/**
	 * Returns a list of strongly referenced EventListeners. Removes internal
	 * weak references to garbage collected listeners.
	 * 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private synchronized <T extends EventListener> List<T> cleanReferences() {
		List<T> listeners = new ArrayList<T>();
		for (int i = getReferences().size() - 1; i >= 0; i--) {

			Object listener = getReferences().get(i).get();
			if (listener == null) {
				getReferences().remove(i);
				getClasses().remove(i);
			} else {
				listeners.add(0, (T) listener);
			}
		}
		return listeners;
	}

	private List<WeakReference<? extends EventListener>> getReferences() {
		if (weakReferences == null) {
			weakReferences = new ArrayList<WeakReference<? extends EventListener>>();
		}
		return weakReferences;
	}

	private List<Class<? extends EventListener>> getClasses() {
		if (classes == null) {
			classes = new ArrayList<Class<? extends EventListener>>();

		}
		return classes;
	}

	/**
	 * Return an array of all the listeners of the given type. As a side-effect,
	 * cleans out any garbage collected listeners before building the array.
	 * 
	 * @return all of the listeners of the specified type.
	 * @exception ClassCastException
	 *                if the supplied class is not assignable to EventListener
	 * 
	 * @since 1.3
	 */
	@SuppressWarnings("unchecked")
	public <T extends EventListener> T[] getListeners(Class<T> t) {
		List<T> liveListeners = cleanReferences();
		List<T> listeners = new ArrayList<T>();
		for (int i = 0; i < liveListeners.size(); i++) {
			if (getClasses().get(i) == t) {
				listeners.add(liveListeners.get(i));
			}
		}
		T[] result = (T[]) Array.newInstance(t, listeners.size());
		return listeners.toArray(result);
	}

	/**
	 * Adds the listener as a listener of the specified type. As a side-effect,
	 * cleans out any garbage collected listeners before adding.
	 * 
	 * @param t
	 *            the type of the listener to be added
	 * @param l
	 *            the listener to be added
	 */
	public synchronized <T extends EventListener> void add(Class<T> t, T l) {
		if (l == null) {
			// In an ideal world, we would do an assertion here
			// to help developers know they are probably doing
			// something wrong
			return;
		}
		if (!t.isInstance(l)) {
			throw new IllegalArgumentException("Listener " + l + " is not of type " + t);
		}
		cleanReferences();
		getReferences().add(new WeakReference<T>(l));
		getClasses().add(t);
	}

	/**
	 * Removes the listener as a listener of the specified type.
	 * 
	 * @param t
	 *            the type of the listener to be removed
	 * @param l
	 *            the listener to be removed
	 */
	public synchronized <T extends EventListener> void remove(Class<T> t, T l) {
		if (l == null) {
			// In an ideal world, we would do an assertion here
			// to help developers know they are probably doing
			// something wrong
			return;
		}
		if (!t.isInstance(l)) {
			throw new IllegalArgumentException("Listener " + l + " is not of type " + t);
		}
		for (int i = 0; i < getReferences().size(); i++) {
			if (l.equals(getReferences().get(i).get()) && (t == getClasses().get(i))) {
				getReferences().remove(i);
				getClasses().remove(i);
				break;
			}
		}
	}

	// // Serialization support.
	// private void writeObject(ObjectOutputStream s) throws IOException {
	// Object[] lList = listenerList;
	// s.defaultWriteObject();
	//
	// // Save the non-null event listeners:
	// for (int i = 0; i < lList.length; i+=2) {
	// Class t = (Class)lList[i];
	// EventListener l = (EventListener)lList[i+1];
	// if ((l!=null) && (l instanceof Serializable)) {
	// s.writeObject(t.getName());
	// s.writeObject(l);
	// }
	// }
	//
	// s.writeObject(null);
	// }
	//
	// private void readObject(ObjectInputStream s)
	// throws IOException, ClassNotFoundException {
	// listenerList = NULL_ARRAY;
	// s.defaultReadObject();
	// Object listenerTypeOrNull;
	//
	// while (null != (listenerTypeOrNull = s.readObject())) {
	// ClassLoader cl = Thread.currentThread().getContextClassLoader();
	// EventListener l = (EventListener)s.readObject();
	// add((Class<EventListener>)Class.forName((String)listenerTypeOrNull, true,
	// cl), l);
	// }
	// }

	// /**
	// * Returns a string representation of the EventListenerList.
	// */
	// public String toString() {
	// Object[] lList = listenerList;
	// String s = "EventListenerList: ";
	// s += lList.length/2 + " listeners: ";
	// for (int i = 0 ; i <= lList.length-2 ; i+=2) {
	// s += " type " + ((Class)lList[i]).getName();
	// s += " listener " + lList[i+1];
	// }
	// return s;
	// }
}
